﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.model;
using QuickGraph;
using System.Collections.ObjectModel;
using System.Windows;
using System.ComponentModel;
using snat.model.Factories;
using System.Threading;
using snat.Exceptions;

namespace gui {

    /// <summary>
    /// Network Model Connection class controls the interaction logic
    /// for dealing with Network Model
    /// </summary>
    public sealed class NetworkModelConnect {

        // NetworkModelConnect events
        public static event guiFixLayoutEvent RelayoutEvent;
        public static event guiFreezeGraphLayoutEvent FreezeGraphEvent;
        public static event guiOnConnectingEvent OnConnectingEvent;
        public static event guiOnConnectedEvent OnConnectedEvent;
        public static event guiTapeLoadingEvent TapeLoadingEvent;
        public static event guiTapeLoadedEvent TapeLoadedEvent;
        public static event guiOnFinishedProcessingEvent OnFinishedProcessingEvent;
        public static event guiOnErrorEvent OnErrorEvent;

        private static NetworkModelConnect instance;
        public static NetworkModelConnect Instance {
            get {
                if (instance == null) instance = new NetworkModelConnect();
                return instance;
            }
        }

        // Events that the NetworkViewModel listen to
        public event nodeDelegate OnNodeAdded;
        private void NodeAdded(ClusterNode b) {
            if (OnNodeAdded != null) OnNodeAdded(b);
        }
        public event edgeDelegate OnEdgeAdded;
        private void EdgeAdded(Edge e) {
            if (OnEdgeAdded != null) OnEdgeAdded(e);
        }
        public event nodeDelegate OnNodeRemoved;
        private void NodeRemoved(ClusterNode b) {
            if (OnNodeRemoved != null) OnNodeRemoved(b);
        }
        public event edgeDelegate OnEdgeRemoved;
        private void EdgeRemoved(Edge e) {
            if (OnEdgeRemoved != null) OnEdgeRemoved(e);
        }

        // These are the lists that NetworkViewModel are bound to
        private Dictionary<int, BaseNode> nodes;
        public Dictionary<int, BaseNode> Nodes {
            get {
                if (nodes == null) nodes = new Dictionary<int, BaseNode>();
                return nodes;
            }
        }
        private List<Edge> edges;
        public List<Edge> Edges {
            get {
                if (edges == null) edges = new List<Edge>();
                return edges;
            }
        }
        private bool printDebugs = false;

        // Storage lists used to hold new nodes and edges while not running with visuals
        public Dictionary<int, BaseNode> NodeStorage = new Dictionary<int, BaseNode>();
        public List<Edge> EdgeStorage = new List<Edge>();

        // Used to control behaviour when the Run With Visualisation checkbox is changed
        private bool runWithVisuals = true;
        public bool RunWithVisuals {
            get { return runWithVisuals; }
            set {
                runWithVisuals = value;
                if (value == true) {
                    StatusInfo.SetStatus("Drawing graph");
                    LoadFromStorage();
                    if (RelayoutEvent != null) RelayoutEvent();
                }
            }
        }

        // Temporary "Running Without Visualisation" Node
        private ClusterNode visNode = new ClusterNode(-1, " ", -1);
        public ClusterNode VisNode {
            get { return visNode; }
        }

        public NetworkModelConnect() {
            // Ensure we have a node in the initial nodes list on initialisation
            if (Nodes.Count == 0) Nodes.Add(VisNode.ID, VisNode);
        }

        private bool firstRun = true;
        private void CleanGraph() {
            if (firstRun) {
                firstRun = false;
                return;
            } else {
                // Clean the graph
                GraphStats.Instance.Clean();
                List<Edge> deleteEdges = new List<Edge>();
                List<BaseNode> deleteNodes = new List<BaseNode>();

                foreach (Edge e in Edges) deleteEdges.Add(e);
                foreach (Edge e in deleteEdges) OnEdgeRemoved(e);
                foreach (BaseNode b in Nodes.Values) {
                    if (b.ID != VisNode.ID && b != null) deleteNodes.Add(b);
                }
                foreach (ClusterNode b in deleteNodes) Instance.RemoveVertex(b);
                deleteEdges.Clear();
                deleteNodes.Clear();
                Edges.Clear();
                NetworkViewModel.ProcessGraph();
            }
        }

        // Load new nodes and edges from storage into the graph
        private void LoadFromStorage() {
            if (runWithVisuals) {
                if (EdgeStorage.Count > 0 || NodeStorage.Count > 0) {
                    if(printDebugs) Console.WriteLine("GUI :: Loading {0} new Nodes and {1} new Edges from storage", NodeStorage.Count, EdgeStorage.Count);

                    // Open Loading Box
                    Thread thread = new Thread(() => {
                        LoadingBox lb = new LoadingBox("Loading Visualisation. Note: This may take a long time.");
                        lb.Show(); 

                        lb.Closed += (sender2, e2) => lb.Dispatcher.InvokeShutdown();
                        System.Windows.Threading.Dispatcher.Run();
                    });
                    thread.SetApartmentState(ApartmentState.STA);
                    thread.Start();

                    // Upload all nodes/edges in storage to the nodes/edges lists
                    foreach (BaseNode n in NodeStorage.Values) if(!NetworkViewModel.Instance.Vertices.Contains(n)) Instance.AddVertex(n.ID, n);
                    foreach (Edge e in EdgeStorage) if (!NetworkViewModel.Instance.Edges.Contains(e)) Instance.AddEdge(e);
                    NodeStorage.Clear();
                    EdgeStorage.Clear();
                }
            }
            // Update graph
            NetworkViewModel.ProcessGraph();
        }


        // MAIN ENTRY POINT FOR NEW DB CONNECTION
        public void ConnectToDataBase(ConnectArgs cArgs) {
            CleanUp();
            if (OnConnectingEvent != null) OnConnectingEvent();

            // Run up connect worker
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += WorkerConnect;
            worker.RunWorkerAsync(cArgs);
            worker.RunWorkerCompleted += FinishedConnect;
        }

        // Connect to the Database
        public static bool IsConnected { get; private set; }
        private static void WorkerConnect(object sender, DoWorkEventArgs e) {
            StatusInfo.SetStatus("Connecting to the Database");
            ConnectArgs cArgs = e.Argument as ConnectArgs;
            try {
                NetworkModel.Instance.Connect(cArgs.URL, cArgs.Port, cArgs.Name, cArgs.User, cArgs.Pass);
            } catch (Exception ex) {
                if (OnErrorEvent != null) OnErrorEvent();
                StatusInfo.SetStatus("Could not connect to the Database.");
                MessageBox.Show("Warning : Can not connect to the specified database. Please ensure you have provided correct access information.\n\nAdvanced:\n" + ex.Message, "Warning", MessageBoxButton.OK, MessageBoxImage.Error);
                e.Cancel = true;
            }
        }
        public void FinishedConnect(object sender, RunWorkerCompletedEventArgs e) {
            if (e.Error != null || e.Cancelled == true) {
                Console.WriteLine("ERROR: " + e.ToString());
            } else {
                if (OnConnectedEvent != null) OnConnectedEvent();   // notify everyone that we successfully connected
                IsConnected = true;
                StatusInfo.SetStatus("Waiting for Algorithm Selection");

                // Run up Algorithm Selector
                InitialiseBox box = new InitialiseBox();
                box.Show();
            }
        }

        public static INodeFactory SelectedFactory { get; private set; }
        public static ObservableCollection<AlgorithmItem> SelectedAlgorithms { get; private set; }
        public void Init(INodeFactory f, ObservableCollection<AlgorithmItem> a) {
            if (TapeLoadingEvent != null) TapeLoadingEvent();
            SelectedFactory = f;
            SelectedAlgorithms = a;

            // Run up network init worker
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += WorkerInit;
            worker.RunWorkerAsync();
            worker.RunWorkerCompleted += FinishedInit;
        }

        // Initialise the Network Model
        private static void WorkerInit(object sender, DoWorkEventArgs e) {
            StatusInfo.SetStatus("Building Network Model");
            ConsolePane.WriteLine("Initialising Network Model.");
            try {
                Initialise.Instance.InitNetworkModel(SelectedFactory, SelectedAlgorithms);
            } catch (Exception ex) {
                if (OnErrorEvent != null) OnErrorEvent();
                StatusInfo.SetStatus("Could not initialise the Network Model");
                MessageBox.Show("Warning : Can not complete Network Model initialisation with the specified factory. Please ensure your database schema is in the correct format, and your Network Model initialisation arguments are valid.\n\nAdvanced:\n" + ex.Message, "Warning", MessageBoxButton.OK, MessageBoxImage.Error);
                e.Cancel = true;
            }
        }
        public void FinishedInit(object sender, RunWorkerCompletedEventArgs e) {
            if (e.Error != null || e.Cancelled == true) {
                Console.WriteLine("ERROR: " + e.ToString());
            } else {
                if (OnFinishedProcessingEvent != null) OnFinishedProcessingEvent(); // notify everyone else we have loaded init
                StatusInfo.SetStatus("Calculating new graph layout");
                NodeStatsInfo.UpdateDisplay();
                if (FreezeGraphEvent != null) FreezeGraphEvent();
                NetworkViewModel.ProcessGraph();
                if (RelayoutEvent != null) RelayoutEvent();
                if (TapeLoadedEvent != null) TapeLoadedEvent();
                ConsolePane.WriteLine("Network Model initialised.\n");
                StatusInfo.SetStatus("Ready");
            }
        }


        public void Reset() {
            Instance.ResetGraph();
            // Run up network reset worker
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += WorkerReset;
            worker.RunWorkerAsync();
            worker.RunWorkerCompleted += FinishedReset;
        }

        // Reset and initialise the Network Model
        private static void WorkerReset(object sender, DoWorkEventArgs e) {
            StatusInfo.SetStatus("Rebuilding Network Model");
            ConsolePane.WriteLine("\nResetting Network Model.");
            PlayControlConnect.Reset();

            try {
                NetworkModel.Instance.Reset();
            } catch (Exception ex) {
                if (OnErrorEvent != null) OnErrorEvent();
                StatusInfo.SetStatus("Could not reset the Network Model");
                MessageBox.Show("Warning : Can not complete Network Model reset and reinitialisation. Please ensure your database schema is in the correct format, and your Network Model reset/reinitialisation arguments are valid.\n\nAdvanced:\n" + ex.Message, "Warning", MessageBoxButton.OK, MessageBoxImage.Error);
                e.Cancel = true;
            }
        }
        public void FinishedReset(object sender, RunWorkerCompletedEventArgs e) {
            if (e.Error != null || e.Cancelled == true) {
                ConsolePane.WriteLine("ERROR: " + e.ToString());
            } else {
                if (OnFinishedProcessingEvent != null) OnFinishedProcessingEvent(); // notify everyone else we have loaded init
                StatusInfo.SetStatus("Calculating new graph layout");
                NodeStatsInfo.UpdateDisplay();
                if (FreezeGraphEvent != null) FreezeGraphEvent();
                NetworkViewModel.ProcessGraph();
                if (RelayoutEvent != null) RelayoutEvent();
                if (TapeLoadedEvent != null) TapeLoadedEvent();
                StatusInfo.SetStatus("Ready");
                ConsolePane.WriteLine("Network Model Reset.\n");
            }
        }

        // Clean up graph on reset
        private void ResetGraph() {
            if (firstRun) {
                firstRun = false;
                return;
            } else {
                // Reset the graph
                GraphStats.Instance.Clean();
                List<Edge> deleteEdges = new List<Edge>();

                foreach (Edge e in Edges) deleteEdges.Add(e);
                foreach (Edge e in deleteEdges) OnEdgeRemoved(e);
                deleteEdges.Clear();
                Edges.Clear();
                NetworkViewModel.ProcessGraph();
            }
        }

        // Clean up
        public void CleanUp() {
            IsConnected = false;
            CleanGraph();
        }

        // *********** EVENT LISTENERS ***********

        // Event listener method that causes Nodes to be added to the NetworkViewModel graph
        public void AddVertex(int id, BaseNode vertex) {
            if (runWithVisuals) {
                if (vertex != null && !Nodes.ContainsKey(vertex.ID)) {
                    if(printDebugs) Console.WriteLine("Connect :: Adding {0}", vertex.Name);
                    Nodes.Add(vertex.ID, vertex);
                    OnNodeAdded(vertex as ClusterNode);
                }
            } else {
                if (vertex != null && !NodeStorage.ContainsKey(vertex.ID)) {
                    if(printDebugs) Console.WriteLine("Connect :: Storing {0}", vertex.Name);
                    NodeStorage.Add(vertex.ID, vertex);
                }
            }
        }
        // Event listener method that causes Edges to be added to the NetworkViewModel graph
        public void AddEdge(Edge edge) {
            if (runWithVisuals) {
                if(!Edges.Contains(edge)) {
                    if(printDebugs) Console.WriteLine("Connect :: Adding edge {0} -> {1}", edge.Source.Name, edge.Target.Name);
                    Edges.Add(edge);
                    OnEdgeAdded(edge);
                }
            } else {
                if (!EdgeStorage.Contains(edge)) {
                    if(printDebugs) Console.WriteLine("Connect :: Storing edge {0} -> {1}", edge.Source.Name, edge.Target.Name);
                    EdgeStorage.Add(edge);
                }
            }
        }

        // Event listener method that causes Nodes to be removed from the NetworkViewModel graph
        public void RemoveVertex(ClusterNode vertex) {
            if(Nodes.ContainsKey(vertex.ID)) {
                Nodes.Remove(vertex.ID);
                OnNodeRemoved(vertex);
            }
        }

        // Event listener method that causes Edges to be removed from the NetworkViewModel graph
        public void RemoveEdge(Edge edge) {
            if(Edges.Contains(edge)) {
                Edges.Remove(edge);
                OnEdgeRemoved(edge);
            }
        }
    }
}